﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Management;

namespace System.Peppers.Wmi
{
    /// <summary>
    /// The Win32_Printer WMI class represents a device connected to a computer running on a Microsoft Windows operating system that can produce a printed image or text on paper or other medium.
    /// </summary>
    public sealed class Printer : CimSetting
    {
        #region Static
        private static class WmiProperties
        {
            public const string Attributes = "Attributes",
                AvailableJobSheets = "AvailableJobSheets",
                AveragePagesPerMinute = "AveragePagesPerMinute",
                Capabilities = "Capabilities",
                CharSetsSupported = "CharSetsSupported",
                Comment = "Comment",
                CurrentCapabilities = "CurrentCapabilities",
                CurrentCharSet = "CurrentCharSet",
                CurrentLanguage = "CurrentLanguage",
                CurrentNaturalLanguage = "CurrentNaturalLanguage",
                CurrentPaperType = "CurrentPaperType",
                DefaultCapabilities = "DefaultCapabilities",
                DefaultLanguage = "DefaultLanguage",
                DefaultCopies = "DefaultCopies",
                DefaultNumberUp = "DefaultNumberUp",
                DefaultPaperType = "DefaultPaperType",
                DefaultPriority = "DefaultPriority",
                DetectedErrorState = "DetectedErrorState",
                DeviceID = "DeviceID",
                Direct = "Direct",
                DoCompleteFirst = "DoCompleteFirst",
                DriverName = "DriverName",
                EnableBIDI = "EnableBIDI",
                EnableDevQueryPrint = "EnableDevQueryPrint",
                Hidden = "Hidden",
                HorizontalResolution = "HorizontalResolution",
                JobCountSinceLastReset = "JobCountSinceLastReset",
                KeepPrintedJobs = "KeepPrintedJobs",
                LanguagesSupported = "LanguagesSupported",
                Local = "Local",
                Location = "Location",
                MaxCopies = "MaxCopies",
                MaxNumberUp = "MaxNumberUp",
                MaxSizeSupported = "MaxSizeSupported",
                Name = "Name",
                NaturalLanguagesSupported = "NaturalLanguagesSupported",
                Network = "Network",
                PaperSizesSupported = "PaperSizesSupported",
                PaperTypesAvailable = "PaperTypesAvailable",
                Parameters = "Parameters",
                PortName = "PortName",
                PrinterPaperNames = "PrinterPaperNames",
                PrinterState = "PrinterState",
                PrinterStatus = "PrinterStatus",
                PrintJobDataType = "PrintJobDataType",
                PrintProcessor = "PrintProcessor",
                Priority = "Priority",
                Published = "Published",
                Queued = "Queued",
                RawOnly = "RawOnly",
                SeparatorFile = "SeparatorFile",
                ServerName = "ServerName",
                Shared = "Shared",
                ShareName = "ShareName",
                StartTime = "StartTime",
                TimeOfLastReset = "TimeOfLastReset",
                UntilTime = "UntilTime",
                VerticalResolution = "VerticalResolution",
                WorkOffline = "WorkOffline";
        }

        private static class WmiMethods
        {
            public const string AddPrinterConnection = "AddPrinterConnection",
                CancelAllJobs = "CancelAllJobs",
                Pause = "Pause",
                PrintTestPage = "PrintTestPage",
                RenamePrinter = "RenamePrinter",
                Resume = "Resume",
                SetDefaultPrinter = "SetDefaultPrinter";
        }

        private static class WmiParameters
        {
            public const string Name = "Name",
                NewPrinterName = "NewPrinterName";
        }

        /// <summary>
        /// Enumerates printers on the system
        /// </summary>
        /// <returns>List of printers</returns>
        public static IEnumerable<Printer> GetPrinters()
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_Printer))
            {
                yield return new Printer(item);
            }
        }

        /// <summary>
        /// Enumerates printers on a remote system
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <returns>List of printers</returns>
        public static IEnumerable<Printer> GetPrinters(WmiRemoteParameters remoteParams)
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_Printer, remoteParams))
            {
                yield return new Printer(item);
            }
        }

        private static class Util
        {
            public static PrinterCapabilities[] ToCapabilities(ushort[] array)
            {
                if (array == null)
                {
                    return null;
                }

                PrinterCapabilities[] ret = new PrinterCapabilities[array.Length];
                for (int i = 0; i < array.Length; i++)
                {
                    ret[i] = (PrinterCapabilities)array[i];
                }
                return ret;
            }

            public static PrinterLanguage[] ToLanguages(ushort[] array)
            {
                if (array == null)
                {
                    return null;
                }

                PrinterLanguage[] ret = new PrinterLanguage[array.Length];
                for (int i = 0; i < array.Length; i++)
                {
                    ret[i] = (PrinterLanguage)array[i];
                }
                return ret;
            }
        }
        #endregion

        internal Printer(ManagementObject wmiObject)
            : base(wmiObject)
        { }

        /// <summary>
        /// Bitmap of attributes for a Windows-based printing device.
        /// </summary>
        public PrinterAttributes Attributes
        {
            get { return (PrinterAttributes)Convert.ToUInt32(this[WmiProperties.Attributes] ?? 0); }
        }

        /// <summary>
        /// Array of all the job sheets available on a printer. Can also be used to describe the banner that a printer might provide at the beginning of each job, or other user-specified options.
        /// </summary>
        public string[] AvailableJobSheets
        {
            get { return this[WmiProperties.AvailableJobSheets] as string[]; }
        }

        /// <summary>
        /// Printing rate, in average number of pages per minute, that a printer can produce output.
        /// </summary>
        public uint AveragePagesPerMinute
        {
            get { return Convert.ToUInt32(this[WmiProperties.AveragePagesPerMinute] ?? 0); }
        }

        /// <summary>
        /// Array of printer capabilities.
        /// </summary>
        public PrinterCapabilities[] Capabilities
        {
            get { return Util.ToCapabilities(this[WmiProperties.AveragePagesPerMinute] as ushort[]); }
        }

        /// <summary>
        /// Array of available character sets for output. Strings provided in this property must conform to the semantics and syntax specified by section 4.1.2 ("Charset parameters") in RFC 2046 (MIME Part 2) and contained in the IANA character-set registry. Examples include, UTF-8, us-ASCII, and iso-8859-1.
        /// </summary>
        public string[] CharSetsSupported
        {
            get { return this[WmiProperties.CharSetsSupported] as string[]; }
        }

        /// <summary>
        /// Comment for a print queue.
        /// </summary>
        public string Comment
        {
            get { return this[WmiProperties.Comment] as string; }
            set { this[WmiProperties.Comment] = value; }
        }

        /// <summary>
        /// Array of printer capabilities that are being used currently. An entry in this property must also be listed in the Capabilities array.
        /// </summary>
        public PrinterCapabilities[] CurrentCapabilities
        {
            get { return Util.ToCapabilities(this[WmiProperties.CurrentCapabilities] as ushort[]); }
        }

        /// <summary>
        /// The character set currently used for output. Strings provided in this property must conform to the semantics and syntax specified by section 4.1.2 ("Charset parameters") in RFC 2046 (MIME Part 2) and contained in the IANA character-set registry. Examples include, utf-8, us-ASCII, and iso-8859-1.
        /// </summary>
        public string CurrentCharSet
        {
            get { return this[WmiProperties.CurrentCharSet] as string; }
        }

        /// <summary>
        /// Printer language currently used. The language used must be listed in the LanguagesSupported property. 
        /// </summary>
        public PrinterLanguage CurrentLanguage
        {
            get { return (PrinterLanguage)Convert.ToUInt16(this[WmiProperties.CurrentLanguage] ?? 0); }
        }

        /// <summary>
        /// Language that the printer is using for management currently. The language listed here must also be listed in the NaturalLanguagesSupported property. 
        /// </summary>
        public string CurrentNaturalLanguage
        {
            get { return this[WmiProperties.CurrentNaturalLanguage] as string; }
        }

        /// <summary>
        /// Type of paper the printer is using. Must be expressed in the form specified by the ISO/IEC 10175 Document Printing Application (DPA), which is summarized in Appendix C of RFC 1759 (Printer MIB).
        /// </summary>
        public string CurrentPaperType
        {
            get { return this[WmiProperties.CurrentPaperType] as string; }
        }

        /// <summary>
        /// If TRUE, the printer is the default printer.
        /// </summary>
        public bool Default
        {
            get { return Convert.ToBoolean(this[WmiProperties.CurrentPaperType] ?? false); }
        }

        /// <summary>
        /// Array of the printer capabilities used by default. Each entry in the DefaultCapabilities array must also be listed in the Capabilities array.
        /// </summary>
        public PrinterCapabilities[] DefaultCapabilities
        {
            get { return Util.ToCapabilities(this[WmiProperties.DefaultCapabilities] as ushort[]); }
        }

        /// <summary>
        /// Number of copies produced for one job—unless otherwise specified.
        /// </summary>
        public uint DefaultCopies
        {
            get { return Convert.ToUInt32(this[WmiProperties.DefaultCopies] ?? 0); }
        }

        /// <summary>
        /// Default printer language. The language listed here must also be listed in the LanguagesSupported property. 
        /// </summary>
        public PrinterLanguage DefaultLanguage
        {
            get { return (PrinterLanguage)Convert.ToUInt16(this[WmiProperties.DefaultLanguage] ?? 0); }
        }

        /// <summary>
        /// Number of print-stream pages that the printer renders on one media sheet—unless a job specifies otherwise. 
        /// </summary>
        public uint DefaultNumberUp
        {
            get { return Convert.ToUInt32(this[WmiProperties.DefaultNumberUp] ?? 0); }
        }

        /// <summary>
        /// Paper type that the printer uses—unless a print job specifies a different paper type. The string must be expressed in the form specified by ISO/IEC 1017 Document Printing Application (DPA), which is summarized in Appendix C of RFC 1759 (Printer MIB). 
        /// </summary>
        public string DefaultPaperType
        {
            get { return this[WmiProperties.DefaultPaperType] as string; }
        }

        /// <summary>
        /// Default priority value assigned to each print job.
        /// </summary>
        public uint DefaultPriority
        {
            get { return Convert.ToUInt32(this[WmiProperties.DefaultPriority] ?? 0); }
        }

        /// <summary>
        /// Printer error information.
        /// </summary>
        public PrinterError DetectedErrorState
        {
            get { return (PrinterError)Convert.ToUInt16(this[WmiProperties.DetectedErrorState] ?? 0); }
        }

        /// <summary>
        /// Unique identifier of the printer on a system. 
        /// </summary>
        public string DeviceID
        {
            get { return this[WmiProperties.DeviceID] as string; }
        }

        /// <summary>
        /// If TRUE, the print job is sent directly to the printer. If FALSE, the print job is spooled.
        /// </summary>
        public bool Direct
        {
            get { return Convert.ToBoolean(this[WmiProperties.Direct] ?? false); }
        }

        /// <summary>
        /// If TRUE, the printer starts jobs that are finished spooling. If FALSE, the printer starts jobs in the order that the jobs are received.
        /// </summary>
        public bool DoCompleteFirst
        {
            get { return Convert.ToBoolean(this[WmiProperties.DoCompleteFirst] ?? false); }
        }

        /// <summary>
        /// Name of the Windows printer driver.  Example: Windows NT Fax Driver
        /// </summary>
        public string DriverName
        {
            get { return this[WmiProperties.DriverName] as string; }
        }

        /// <summary>
        /// If TRUE, the printer can print bidirectionally.
        /// </summary>
        public bool BiDirectionalEnabled
        {
            get { return Convert.ToBoolean(this[WmiProperties.EnableBIDI] ?? false); }
        }

        /// <summary>
        /// If TRUE, the printer holds documents in the queue when document and printer setups do not match.
        /// </summary>
        public bool EnableDevQueryPrint
        {
            get { return Convert.ToBoolean(this[WmiProperties.EnableDevQueryPrint] ?? false); }
        }

        /// <summary>
        /// If TRUE, the printer is hidden from network users.
        /// </summary>
        public bool Hidden
        {
            get { return Convert.ToBoolean(this[WmiProperties.Hidden] ?? false); }
            set { this[WmiProperties.Hidden] = value; }
        }

        /// <summary>
        /// Horizontal resolution of the printer—in pixels per inch. 
        /// </summary>
        public uint HorizontalResolution
        {
            get { return Convert.ToUInt32(this[WmiProperties.HorizontalResolution] ?? 0); }
        }

        /// <summary>
        /// Number of print jobs since the printer was last reset.
        /// </summary>
        public uint JobCountSinceLastReset
        {
            get { return Convert.ToUInt32(this[WmiProperties.JobCountSinceLastReset] ?? 0); }
        }

        /// <summary>
        /// If TRUE, the print spooler does not delete the completed jobs.
        /// </summary>
        public bool KeepPrintedJobs
        {
            get { return Convert.ToBoolean(this[WmiProperties.KeepPrintedJobs] ?? false); }
            set { this[WmiProperties.KeepPrintedJobs] = value; }
        }

        /// <summary>
        /// Array of the print languages natively supported. 
        /// </summary>
        public PrinterLanguage[] LanguagesSupported
        {
            get { return Util.ToLanguages(this[WmiProperties.LanguagesSupported] as ushort[]); }
        }

        /// <summary>
        /// If TRUE, the printer is not attached to a network. If both the Local and Network properties are set to TRUE, then the printer is a network printer.
        /// </summary>
        public bool Local
        {
            get { return Convert.ToBoolean(this[WmiProperties.Local] ?? false); }
            set { this[WmiProperties.Local] = value; }
        }

        /// <summary>
        /// Physical location of the printer.  Example: Bldg. 38, Room 1164
        /// </summary>
        public string Location
        {
            get { return Convert.ToString(this[WmiProperties.Location]); }
            set { this[WmiProperties.Location] = value; }
        }

        /// <summary>
        /// Maximum number of copies the printer can produce for one job.
        /// </summary>
        public uint MaxCopies
        {
            get { return Convert.ToUInt32(this[WmiProperties.MaxCopies] ?? 0); }
        }

        /// <summary>
        /// Maximum number of print-stream pages the printer can render on one media sheet, such as paper.
        /// </summary>
        public uint MaxNumberUp
        {
            get { return Convert.ToUInt32(this[WmiProperties.MaxNumberUp] ?? 0); }
        }

        /// <summary>
        /// Largest job as a byte stream, in kilobytes, that the printer can accept. A value of 0 (zero) indicates that no limit is set.
        /// </summary>
        public uint MaxSizeSupported
        {
            get { return Convert.ToUInt32(this[WmiProperties.MaxSizeSupported] ?? 0); }
        }

        /// <summary>
        /// Name of the printer.
        /// </summary>
        public string Name
        {
            get { return Convert.ToString(this[WmiProperties.Name]); }
        }

        /// <summary>
        /// Array of languages supported for strings that the printer uses for output of management information. Must conform to RFC 1766. For example, "en" is used for English. 
        /// </summary>
        public string[] NaturalLanguagesSupported
        {
            get { return this[WmiProperties.NaturalLanguagesSupported] as string[]; }
        }

        /// <summary>
        /// If TRUE, the printer is a network printer. If both the Local and Network properties are set to TRUE, then the printer is a network printer.
        /// </summary>
        public bool Network
        {
            get { return Convert.ToBoolean(this[WmiProperties.Network] ?? false); }
            set { this[WmiProperties.Network] = value; }
        }

        /// <summary>
        /// Array of paper types that are currently available on the printer. Each string must be expressed in the format specified by ISO/IEC 10175 Document Printing Application (DPA), which is summarized in Appendix C of RFC 1759 (Printer MIB). Any paper size identified in this property must also appear in the PaperSizesSupported property. 
        /// </summary>
        public string[] PaperTypesAvailable
        {
            get { return this[WmiProperties.PaperTypesAvailable] as string[]; }
        }

        /// <summary>
        /// Port that is used to transmit data to a printer. If a printer is connected to more than one port, the names of each port are separated by commas. Under Windows 95, only one port can be specified.  Example: LPT1:, LPT2:, LPT3:
        /// </summary>
        public string PortName
        {
            get { return Convert.ToString(this[WmiProperties.PortName]); }
            set { this[WmiProperties.PortName] = value; }
        }

        /// <summary>
        /// Array of paper sizes supported by the printer. The printer-specified names are used to represent supported paper sizes.  Example: B5 (JIS)
        /// </summary>
        public string[] PrinterPaperNames
        {
            get { return this[WmiProperties.PrinterPaperNames] as string[]; }
        }

        /// <summary>
        /// Data type of a print job waiting for the Windows-based printing device.
        /// </summary>
        public string PrintJobDataType
        {
            get { return Convert.ToString(this[WmiProperties.PrintJobDataType]); }
            set { this[WmiProperties.PrintJobDataType] = value; }
        }

        /// <summary>
        /// Priority of the printer. Jobs on a higher priority printer are scheduled first.
        /// </summary>
        public uint Priority
        {
            get { return Convert.ToUInt32(this[WmiProperties.Priority] ?? 0); }
            set { this[WmiProperties.Priority] = value; }
        }

        /// <summary>
        /// If TRUE, the printer is published in the network directory service.
        /// </summary>
        public bool Published
        {
            get { return Convert.ToBoolean(this[WmiProperties.Published] ?? false); }
            set { this[WmiProperties.Published] = value; }
        }

        /// <summary>
        /// If TRUE, the printer buffers and queues print jobs.
        /// </summary>
        public bool Queued
        {
            get { return Convert.ToBoolean(this[WmiProperties.Queued] ?? false); }
            set { this[WmiProperties.Queued] = value; }
        }

        /// <summary>
        /// If TRUE, the printer accepts only raw data to be spooled.
        /// </summary>
        public bool RawOnly
        {
            get { return Convert.ToBoolean(this[WmiProperties.RawOnly] ?? false); }
            set { this[WmiProperties.RawOnly] = value; }
        }

        /// <summary>
        /// Name of the file used to create a separator page. This page is used to separate print jobs sent to the printer.
        /// </summary>
        public string SeparatorFile
        {
            get { return Convert.ToString(this[WmiProperties.SeparatorFile]); }
            set { this[WmiProperties.SeparatorFile] = value; }
        }

        /// <summary>
        /// Name of the server that controls the printer. If this string is NULL, the printer is controlled locally.
        /// </summary>
        public string ServerName
        {
            get { return Convert.ToString(this[WmiProperties.ServerName]); }
        }

        /// <summary>
        /// If TRUE, the printer is available as a shared network resource.
        /// </summary>
        public bool Shared
        {
            get { return Convert.ToBoolean(this[WmiProperties.Shared] ?? false); }
            set { this[WmiProperties.Shared] = value; }
        }

        /// <summary>
        /// Share name of the Windows-based printing device.  Example: \\PRINTSERVER1\PRINTER2
        /// </summary>
        public string ShareName
        {
            get { return Convert.ToString(this[WmiProperties.ShareName]); }
            set { this[WmiProperties.ShareName] = value; }
        }

        /// <summary>
        /// Date and time that a printer can start to print a job—if the printer is limited to print at specific times. This value is expressed as the time elapsed since 12:00 AM GMT (Greenwich Mean Time).
        /// </summary>
        public DateTime? StartTime
        {
            get { return WmiUtil.ToDateTime(this[WmiProperties.StartTime] ?? DateTime.MinValue); }
            set { this[WmiProperties.StartTime] = WmiUtil.ToWmiTime(value); }
        }

        /// <summary>
        /// Date and time the printer was last reset.
        /// </summary>
        public DateTime? TimeOfLastReset
        {
            get { return WmiUtil.ToDateTime(this[WmiProperties.TimeOfLastReset]); }
        }

        /// <summary>
        /// Date and time that a printer can print the last job—if the printer is limited to print at specific times. This value is expressed as the time elapsed since 12:00 AM GMT (Greenwich Mean Time).
        /// </summary>
        public DateTime? UntilTime
        {
            get { return WmiUtil.ToDateTime(this[WmiProperties.UntilTime] ?? DateTime.MinValue); }
            set { this[WmiProperties.UntilTime] = WmiUtil.ToWmiTime(value); }
        }

        /// <summary>
        /// Vertical resolution, in pixels-per-inch, of the printer. 
        /// </summary>
        public uint VerticalResolution
        {
            get { return Convert.ToUInt32(this[WmiProperties.VerticalResolution] ?? 0); }
        }

        /// <summary>
        /// If TRUE, you can queue print jobs on the computer when the printer is offline.
        /// </summary>
        public bool WorkOffline
        {
            get { return Convert.ToBoolean(this[WmiProperties.WorkOffline] ?? false); }
            set { this[WmiProperties.WorkOffline] = value; }
        }

        /// <summary>
        /// The CancelAllJobs WMI class method removes all jobs, including the one currently printing from the queue.
        /// </summary>
        /// <returns>Result of method</returns>
        public PrinterResult CancelAllJobs()
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.CancelAllJobs, null);
        }

        /// <summary>
        /// The Pause WMI class method pauses the print queue. No jobs can print until the queue is resumed.
        /// </summary>
        /// <returns>Result of method</returns>
        public PrinterResult Pause()
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.Pause, null);
        }

        /// <summary>
        /// The AddPrinterConnection WMI class method provides a connection to an existing printer on the network, and adds it to the list of available printers.
        /// </summary>
        /// <param name="name">Friendly name for the printer.</param>
        /// <returns>Result of method</returns>
        public PrinterResult AddPrinterConnection(string name)
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.AddPrinterConnection, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.Name] = name;
            });
        }

        /// <summary>
        /// The PrintTestPage WMI class method prints a test page. 
        /// </summary>
        /// <returns>Result of method</returns>
        public PrinterResult PrintTestPage()
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.PrintTestPage, null);
        }

        /// <summary>
        /// The RenamePrinter WMI class method renames a printer.
        /// </summary>
        /// <param name="newPrinterName">New printer name.</param>
        /// <returns>Result of method</returns>
        public PrinterResult RenamePrinter(string newPrinterName)
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.RenamePrinter, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.NewPrinterName] = newPrinterName;
            });
        }

        /// <summary>
        /// The Resume WMI class method resumes a paused print queue.
        /// </summary>
        /// <returns>Result of method</returns>
        public PrinterResult Resume()
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.Resume, null);
        }

        /// <summary>
        /// The SetDefaultPrinter WMI class method sets the default system printer for the user calling the method.
        /// </summary>
        /// <returns>Result of method</returns>
        public PrinterResult SetDefaultPrinter()
        {
            return (PrinterResult)ExecuteMethodWithResult(WmiMethods.SetDefaultPrinter, null);
        }
    }
}